package cn.feizhu.aimarket.ratelimit.aspect;

import cn.dev33.satoken.stp.StpUtil;
import cn.feizhu.aimarket.exception.BusinessException;
import cn.feizhu.aimarket.exception.ErrorCode;
import cn.feizhu.aimarket.ratelimit.annotation.RateLimit;
import cn.feizhu.aimarket.ratelimit.enums.RateLimitType;
import cn.feizhu.aimarket.service.UserService;
import cn.feizhu.aimarket.model.vo.UserVO;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RRateLimiter;
import org.redisson.api.RateIntervalUnit;
import org.redisson.api.RateType;
import org.redisson.api.RedissonClient;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import jakarta.servlet.http.HttpServletRequest;

import java.time.Duration;
import java.util.Date;

@Aspect
@Component
@Slf4j
public class RateLimitAspect {

    @Resource
    private RedissonClient redissonClient;

    @Resource
    private UserService userService;

    @Before("@annotation(rateLimit)")
    @Order(1)
    public void doBefore(JoinPoint joinPoint, RateLimit rateLimit) {
        // 计算实际限流参数（VIP优先使用覆盖值，否则倍率）
        int actualRate;
        if (isVipUser()) {
            actualRate = rateLimit.vipRateOverride() > 0
                    ? rateLimit.vipRateOverride()
                    : rateLimit.rate() * rateLimit.vipMultiplier();
        } else {
            actualRate = rateLimit.rate();
        }

        String key = generateRateLimitKey(joinPoint, rateLimit);
        RRateLimiter rateLimiter = redissonClient.getRateLimiter(key);

        // 先配置限流器，再设置过期时间
        boolean isNewRateLimiter = rateLimiter.trySetRate(RateType.OVERALL, actualRate, rateLimit.rateInterval(),
                              toRateIntervalUnit(rateLimit.timeUnit()));

        // 如果是新创建的限流器或者需要重新设置过期时间，则设置TTL
        if (isNewRateLimiter || rateLimiter.remainTimeToLive() <= 0) {
            // 根据限流时间单位设置合理的过期时间
            Duration expireDuration = calculateExpireDuration(rateLimit.rateInterval(), rateLimit.timeUnit());
            rateLimiter.expire(expireDuration);
            log.debug("设置限流器过期时间 - key: {}, expireDuration: {}", key, expireDuration);
        }

        // 记录限流器状态（调试用）
        log.info("限流检查 - key: {}, 可用令牌: {}, 限制: {}/{} {}, TTL: {}ms",
                 key, rateLimiter.availablePermits(), actualRate, rateLimit.rateInterval(),
                 rateLimit.timeUnit(), rateLimiter.remainTimeToLive());

        // 尝试获取令牌
        if (!rateLimiter.tryAcquire(1)) {
            // 记录限流违例次数（每日维度），并打印警告日志
            try {
                org.redisson.api.RMapCache<String, Long> violations = redissonClient.getMapCache("rate_limit:violations");
                String dailyKey = key + ":" + java.time.LocalDate.now();
                Long count = violations.get(dailyKey);
                long newCount = (count == null ? 1L : count + 1L);
                violations.put(dailyKey, newCount, 2, java.util.concurrent.TimeUnit.DAYS);
                log.warn("Rate limit exceeded - key={}, countToday={}, availablePermits={}",
                        key, newCount, rateLimiter.availablePermits());
            } catch (Exception ignore) {}
            throw new BusinessException(ErrorCode.FORBIDDEN_ERROR, rateLimit.message());
        }

        log.info("限流通过 - key: {}, 剩余令牌: {}", key, rateLimiter.availablePermits());
    }

    private boolean isVipUser() {
        try {
            Long userId = StpUtil.getLoginIdAsLong();
            if (userId == null) return false;

            UserVO userInfo = userService.getUserInfo(userId);
            Date vipExpireTime = userInfo.getVipExpireTime();
            return vipExpireTime != null && vipExpireTime.after(new Date());
        } catch (Exception e) {
            return false;
        }
    }

    private String generateRateLimitKey(JoinPoint joinPoint, RateLimit rateLimit) {
        StringBuilder key = new StringBuilder("rate_limit:");

        if (!rateLimit.key().isEmpty()) {
            key.append(rateLimit.key()).append(":");
        }

        switch (rateLimit.limitType()) {
            case API:
                MethodSignature signature = (MethodSignature) joinPoint.getSignature();
                key.append("api:")
                        .append(signature.getMethod().getDeclaringClass().getSimpleName())
                   .append(".").append(signature.getMethod().getName());
                break;
            case USER:
                try {
                    Long userId = StpUtil.getLoginIdAsLong();
                    key.append("user:").append(userId != null ? userId : getClientIp());
                } catch (Exception e) {
                    key.append("ip:").append(getClientIp());
                }
                break;
            case IP:
                key.append("ip:").append(getClientIp());
                break;
        }
        return key.toString();
    }

    private String getClientIp() {
        try {
            ServletRequestAttributes attrs = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            if (attrs == null) return "unknown";

            HttpServletRequest request = attrs.getRequest();
            String ip = request.getHeader("X-Forwarded-For");
            if (ip != null && !ip.isBlank()) {
                return ip.split(",")[0].trim();
            }

            ip = request.getHeader("X-Real-IP");
            if (ip != null && !ip.isBlank()) {
                return ip.trim();
            }

            return request.getRemoteAddr();
        } catch (Exception e) {
            return "unknown";
        }
    }

    private RateIntervalUnit toRateIntervalUnit(java.util.concurrent.TimeUnit timeUnit) {
        switch (timeUnit) {
            case SECONDS:
                return RateIntervalUnit.SECONDS;
            case MINUTES:
                return RateIntervalUnit.MINUTES;
            case HOURS:
                return RateIntervalUnit.HOURS;
            default:
                return RateIntervalUnit.SECONDS;
        }
    }

    /**
     * 根据限流时间单位计算合理的过期时间
     * 过期时间应该比限流周期长一些，以确保限流器能正常工作
     */
    private Duration calculateExpireDuration(long rateInterval, java.util.concurrent.TimeUnit timeUnit) {
        long intervalInSeconds;
        switch (timeUnit) {
            case SECONDS:
                intervalInSeconds = rateInterval;
                break;
            case MINUTES:
                intervalInSeconds = rateInterval * 60;
                break;
            case HOURS:
                intervalInSeconds = rateInterval * 3600;
                break;
            case DAYS:
                intervalInSeconds = rateInterval * 86400;
                break;
            default:
                intervalInSeconds = rateInterval;
        }

        // 过期时间设置为限流周期的2倍，最少1小时，最多7天
        long expireSeconds = Math.max(3600, Math.min(intervalInSeconds * 2, 604800));
        return Duration.ofSeconds(expireSeconds);
    }
}
